#ifndef GDBMI_H
#define GDBMI_H

/*
    ** The communication between gdb and Manna **
    The communication between gdb and Manna based on a
    an asynchronous model, which contains a server
    (an instance of GDBMI) and several clients
    (instances of GDBMIClient). The server will creates
    a process of gdb and listens to its output. When
    output is available, the proper clients will be
    notified. The client can also send commands to gdb
    via the server.

    ** How clients classify the gdb's output **
    Basically, all asynchronous output wiil be forwarded
    to a special client(instance of GDBMIAsyncClient), and
    all synchronous output(gdb's synchronous output
    corresponding to previous command) will be forwarded
    to the client which has sent a request and expected
    these responses. Therefore, all the client (except the
    special one mentioned above) will only receive the
    output which is corresponding to the request the
    client sent earlier. How this mechanism is implemented
    is largely based on the token associated with each
    command in gdb /mi.

    ** Thread context of client **
    All the client method will run in the main thread context,
    which runs the main event loop for main window.
*/

#include <QProcess>
#include <list>

#include "midef.h"

class GDBMI;

class GDBMIClient
{
public:
    enum STOPREASON {SR_BKPT, SR_EXIT, SR_SIG, SR_OTHER};

    GDBMIClient (GDBMI * pThread);
    virtual ~GDBMIClient () {}

    // Send request to gdb, and the token associated with
    // this request will be returned.
    // -1 if failed to send this request.
    int sendRequest (const QString & request);

    // This method will be called when gdb has started.
    virtual void onStarted () = 0;

    // This method will be called when gdb has exited.
    virtual void onExit () = 0;

    // This method will be called when the target has stopped
    // due to hitting break point, or receving a signal, or
    // any other else
    virtual void onStopped (STOPREASON reason) = 0;

    // This method will be called when the output corresponding
    // to request this client has sent earlier arrive.
    //
    // Notice that sometimes a request will cause several
    // responses received, so you should return false if
    // the result associated with this token will not be
    // received, true otherwise.
    //
    // The space allocated for resultRecord do NOT need to free
    // in this method.
    virtual bool onResultRecord (MIResultRecord * resultRecord) = 0;
    virtual void onResultParseErr(int token) = 0;

protected:
    GDBMI   *m_pGDBMI;
};

class GDBMIAsyncClient : public GDBMIClient
{
public:
    GDBMIAsyncClient (GDBMI * pThread);
    virtual ~GDBMIAsyncClient () {}

    // The following two methods will be called when
    // the corresponding asynchronous output received.
    //
    // For detail, refer to gdb /mi specification

    virtual void onAsyncRecord (MIAsyncRecord * asyncRecord) = 0;
    virtual void onStreamRecord (MIStreamRecord * streamRecord) = 0;
};

class GDBMI : public QObject
{
    Q_OBJECT

    enum { GDB_WAITTIMEOUT = 500 };
public:
    enum { NORMAL, CRITICAL };

    GDBMI (const QString & gdbName);
    ~GDBMI ();

    bool startDebug (const QString & target);

    // The last way to stop gdb when gdb is hung or
    // has no response.
    void stopDebug ();
    void setAsyncClient (GDBMIAsyncClient * client);
    void setGdbDir (const QString & gdbDir);
    void registerClient (GDBMIClient * client);
    void setWorkDirectory (const QString & workDirectory);

    // This method will cause all the clients' onStopped method
    // being called.
    void broadcastStopped (GDBMIClient::STOPREASON reason);

    int sendRequest (const QString & request, GDBMIClient * client);
    bool isStarted () const;
    bool verIsNew ();
    // The current target under debug
    const QString & getTarget () const;

    // Emit message signal
    void emitMessage (QString msg, int level = NORMAL);

    // the pid of process of gdb, 0 if gdb is not running
    Q_PID pid () const;

protected:
    bool launchDebugger ();
    void connectSignals ();
    void disconnectSignals ();

signals:
    void message (const QString &, int level);

protected slots:
    void slotStandardOutput ();
    void slotStandardError ();
    void slotFinish (int exitCode, QProcess::ExitStatus exitStatus);

private:
    GDBMIClient *getClientByToken (int token);
    GDBMIClient *getClientByToken (int token,int flag);
    QStringList breakIntoOutputs (QString & output);

private:
    QString        m_gdbName;
    QString        m_workDirectory;
    QString        m_target;
    QString        m_buffer;

    QProcess    m_debugger;
    bool        m_started;

    bool        m_verIsNew;
    typedef std::pair<int, GDBMIClient *>    RequestPair;

    std::list<GDBMIClient *>        m_syncClients;
    GDBMIAsyncClient                * m_asyncClient;
    std::list<RequestPair>            m_requests;
    int                                m_token;
};

inline void GDBMI::setAsyncClient (GDBMIAsyncClient * client)
{
    m_asyncClient = client;
}

inline void GDBMI::setGdbDir(const QString &gdbDir)
{
    m_gdbName = gdbDir;
}

inline void GDBMI::registerClient (GDBMIClient * client)
{
    m_syncClients.push_back (client);
}

inline GDBMIClient *GDBMI::getClientByToken (int token)
{
    std::list<RequestPair>::iterator it;
    for (it = m_requests.begin ();it != m_requests.end ();++ it)
    {
        if (it->first == token)
        {
            if (it != m_requests.begin ()) {
                m_requests.erase(m_requests.begin (),it);
            }
            GDBMIClient * client = it->second;
            return client;
        }
    }

    return 0;
}

inline GDBMIClient * GDBMI::getClientByToken (int token,int flag)
{
    std::list<RequestPair>::iterator it;
    for (it = m_requests.begin ();it != m_requests.end ();++ it)
    {
        if (it->first == token && flag > 0)
        {
            /*if (it != m_requests.begin ()) {
                m_requests.erase(m_requests.begin (),it);
            }*/
            GDBMIClient * client = it->second;
            return client;
        }
    }

    return 0;
}

inline void GDBMI::setWorkDirectory (const QString & workDirectory)
{
    m_workDirectory = workDirectory;
}

inline void GDBMI::broadcastStopped (GDBMIClient::STOPREASON reason)
{
    //qDebug ("broadcast stopped message to %d clients\n", m_syncClients.size ());
    for (std::list<GDBMIClient *>::iterator it = m_syncClients.begin ();
        it != m_syncClients.end (); ++it)
        (*it)->onStopped(reason);
}

inline bool GDBMI::isStarted () const
{
    return m_started;
}

inline const QString & GDBMI::getTarget () const
{
    return m_target;
}

inline void GDBMI::emitMessage (QString msg, int level)
{
    emit message (msg, level);
}

inline Q_PID GDBMI::pid () const
{
    return m_debugger.pid ();
}

#endif // GDBMI_H
